package com.liuqi.loggers.core.config.xml;

import com.liuqi.loggers.core.Appender;
import com.liuqi.loggers.core.Configuration;
import com.liuqi.loggers.core.Encoder;
import com.liuqi.loggers.core.Filter;
import com.liuqi.loggers.core.Level;
import com.liuqi.loggers.core.rolling.RollingPolicy;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * loggers.xml 读取
 *
 * @author liuqi
 * @date 2024/6/6 17:48
 **/
public class SaxRecorderConfiguration {

    /**
     * xml property node label name
     */
    private static final String LABEL_PROPERTY = "property";

    /**
     * xml appender node label name
     */
    private static final String LABEL_APPENDER = "appender";

    /**
     * xml levels node label name
     */
    private static final String LABEL_LEVELS = "levels";

    /**
     * xml appender-ref node label name
     */
    private static final String LABEL_APPENDER_REF = "appender-ref";

    /**
     * xml rolling-policy node label name
     */
    private static final String LABEL_ROLLING_POLICY = "rolling-policy";

    /**
     * xml encoder node label name
     */
    private static final String LABEL_ENCODER = "encoder";

    /**
     * xml filter node label name
     */
    private static final String LABEL_FILTER = "filter";

    /**
     * xml root node label name
     */
    private static final String LABEL_ROOT = "root";

    /**
     * ${} 表达式
     */
    static Pattern expression = Pattern.compile("(\\$\\{)([\\w\\.]+)(\\})");

    /**
     * 缓存标签: {@link #LABEL_PROPERTY} 属性值
     */
    private final Map<String, String> properties = new HashMap<String, String>();

    /**
     * 缓存 xml 配置的 Appender
     */
    private final Map<String, Appender> appends = new HashMap<String, Appender>();

    /**
     * Configuration
     */
    private final Configuration configuration = new Configuration();

    private final InputSource inputSource;

    private Document document;
    private Element rootElement;

    public SaxRecorderConfiguration(String filepath) throws FileNotFoundException {
        this(new File(filepath));
    }

    public SaxRecorderConfiguration(File file) throws FileNotFoundException {
        this(new FileInputStream(file));
    }

    public SaxRecorderConfiguration(InputStream stream) {
        this(new InputSource(stream));
    }

    public SaxRecorderConfiguration(InputSource inputSource) {
        this.inputSource = inputSource;
    }

    public Configuration record() throws ParserConfigurationException, IOException, SAXException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        document = builder.parse(inputSource);
        document.getDocumentElement().normalize();
        rootElement = document.getDocumentElement();

        parse();

        return configuration;
    }

    private void parse() {
        property();
        appender();
        levels();
        root();
    }

    /**
     * 解析标签: {@link #LABEL_PROPERTY}
     */
    private void property() {
        NodeList nodeList = rootElement.getElementsByTagName(LABEL_PROPERTY);
        for (int i = 0; i < nodeList.getLength(); i++) {
            NamedNodeMap attributes = nodeList.item(i).getAttributes();
            String name = attributes.getNamedItem("name").getNodeValue();
            String value = attributes.getNamedItem("value").getNodeValue();

            if (name == null || name.isEmpty()) {
                throw new NullPointerException("loggers.xml configuration the node label name [property] error, " +
                        "of some nodes the [name] cannot be null.");
            }

            if (value == null || value.isEmpty()) {
                throw new NullPointerException("loggers.xml configuration the node label name [property] error, " +
                        "of some nodes the [value] cannot be null.");
            }

            properties.put(name, value(value));
        }
    }

    /**
     * 解析标签: {@link #LABEL_APPENDER}
     */
    private void appender() {
        NodeList nodeList = rootElement.getElementsByTagName(LABEL_APPENDER);
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            NamedNodeMap attributes = node.getAttributes();
            String name = attributes.getNamedItem("name").getNodeValue();
            String className = attributes.getNamedItem("class").getNodeValue();

            // check exist
            if (appends.containsKey(name)) {
                throw new IllegalArgumentException("appender name [" + name + "] cannot repeat");
            }

            // 反射创建 Appender
            Appender appender = (Appender) newInstance(className);
            // set appender name
            appender.setName(name);

            // cache
            appends.put(name, appender);

            // 解析子节点
            List<Node> nodes = childNodes(node);
            for (Node n : nodes) {
                if (n.getNodeName().equals(LABEL_ENCODER)) {
                    // 解析 encode, 并赋值
                    Reflect.setFiled(appender, "encoder", encoder(n));
                } else if (n.getNodeName().equals(LABEL_FILTER)) {
                    // 解析 filter, 并赋值
                    Reflect.setFiled(appender, "filters", filter(n));
                } else if (n.getNodeName().equals(LABEL_ROLLING_POLICY)) {
                    // 解析 rolling-policy 并赋值
                    Reflect.setFiled(appender, "rollingPolicy", rollingPolicy(n));
                } else if (n.getNodeName().equals(LABEL_APPENDER_REF)) {
                    Reflect.setFiled(appender, "refAppends", appenderRef(n));
                } else {
                    Reflect.setFiled(appender, n.getNodeName(), value(n.getTextContent()));
                }
            }
        }
    }

    /**
     * 解析标签: {@link #LABEL_LEVELS}
     */
    private void levels() {
        NodeList nodeList = rootElement.getElementsByTagName(LABEL_LEVELS);
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node levelsNode = nodeList.item(i);
            List<Node> nodes = childNodes(levelsNode);
            for (Node level : nodes) {
                if ("level".equals(level.getNodeName())) {
                    String name = level.getAttributes().getNamedItem("name").getNodeValue();
                    String levelStr = level.getAttributes().getNamedItem("level").getNodeValue();

                    configuration.addLevel(name, Level.valueOf(levelStr.toUpperCase()));
                }
            }
        }
    }

    /**
     * 解析标签: {@link #LABEL_ROOT}
     */
    private void root() {
        NodeList nodeList = rootElement.getElementsByTagName(LABEL_ROOT);
        if (nodeList.getLength() == 0) {
            throw new NullPointerException("loggers.xml configuration the node label name [root] is no definition");
        }
        if (nodeList.getLength() > 1) {
            throw new NullPointerException("loggers.xml configuration the node label name [root] only one can be defined");
        }
        Node root = nodeList.item(0);

        // 日志级别
        String level = root.getAttributes().getNamedItem("level").getNodeValue();
        if (level == null || level.isEmpty()) {
            throw new NullPointerException("loggers.xml configuration the node label name [root], attribute [level] cannot be null");
        }

        configuration.setLevel(Level.valueOf(level.toUpperCase()));

        List<Node> nodes = childNodes(root);
        for (Node node : nodes) {
            configuration.addAppender(appenderRef(node));
        }
    }

    /**
     * 解析标签: {@link #LABEL_ENCODER}
     *
     * @param encodeNode    标签 {@link #LABEL_ENCODER}
     */
    private Encoder encoder(Node encodeNode) {
        String encodeClass = encodeNode.getAttributes().getNamedItem("class").getNodeValue();
        if (encodeClass == null || encodeClass.isEmpty()) {
            throw new NullPointerException("encode class cannot be null");
        }
        // 反射创建 Encoder 实例
        Encoder encoder = (Encoder) newInstance(encodeClass);

        // 读取属性, 并设置字段值
        List<Node> nodes = childNodes(encodeNode);
        for (Node n : nodes) {
            Reflect.setFiled(encoder, n.getNodeName(), value(n.getTextContent()));
        }

        return encoder;
    }

    /**
     * 解析标签: {@link #LABEL_FILTER}
     *
     * @param filterNode
     * @return
     */
    private Filter filter(Node filterNode) {
        String filterClass = filterNode.getAttributes().getNamedItem("class").getNodeValue();
        if (filterClass == null || filterClass.isEmpty()) {
            throw new NullPointerException("filter class cannot be null");
        }

        // 反射创建 Filter 实例
        Filter filter = (Filter) newInstance(filterClass);

        // 读取属性, 并设置字段值
        List<Node> nodes = childNodes(filterNode);
        for (Node n : nodes) {
            Reflect.setFiled(filter, n.getNodeName(), value(n.getTextContent()));
        }

        return filter;
    }

    /**
     * 解析标签: {@link #LABEL_ROLLING_POLICY}
     *
     * @param rollingPolicyNode
     * @return
     */
    private RollingPolicy rollingPolicy(Node rollingPolicyNode) {
        String rollingPolicyClass = rollingPolicyNode.getAttributes().getNamedItem("class").getNodeValue();
        if (rollingPolicyClass == null || rollingPolicyClass.isEmpty()) {
            throw new NullPointerException("rolling-policy class cannot be null");
        }

        // 反射创建 Filter 实例
        RollingPolicy rollingPolicy = (RollingPolicy) newInstance(rollingPolicyClass);

        // 读取属性, 并设置字段值
        List<Node> nodes = childNodes(rollingPolicyNode);
        for (Node n : nodes) {
            Reflect.setFiled(rollingPolicy, n.getNodeName(), value(n.getTextContent()));
        }

        return rollingPolicy;
    }

    /**
     * 解析标签: {@link #LABEL_APPENDER_REF}
     *
     * @param appenderRefNode
     * @return
     */
    private Appender appenderRef(Node appenderRefNode) {
        String appenderRefName = appenderRefNode.getAttributes().getNamedItem("ref").getNodeValue();
        Appender appender = appends.get(appenderRefName);
        if (appender == null) {
            throw new NullPointerException("appender-ref [" + appenderRefName + "] is no definition");
        }
        return appender;
    }

    Object newInstance(String className) {
        Class c;
        try {
            c = Class.forName(className);
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException("className [" + className + "] cannot new instance ", e);
        }

        Constructor constructor;
        try {
            constructor = c.getConstructor();
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException("className [" + className + "] cannot new instance ", e);
        }

        try {
            return constructor.newInstance();
        } catch (Exception e) {
            throw new IllegalArgumentException("className [" + className + "] cannot new instance ", e);
        }
    }

    List<Node> childNodes(Node parentNode) {
        NodeList childNodes = parentNode.getChildNodes();
        List<Node> nodes = new ArrayList<Node>(childNodes.getLength());
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node node = childNodes.item(i);
            String nodeName = node.getNodeName();
            if (!nodeName.startsWith("#")) {
                nodes.add(node);
            }
        }
        return nodes;
    }

    String value(String value) {
        Matcher matcher = expression.matcher(value);
        StringBuffer buf = new StringBuffer();
        int limit = value.length();
        int position = 0;

        while(matcher.find()) {
            String expr = matcher.group(2);
            String val = getProperty(expr);
            if (val == null) {
                throw new NullPointerException("parse expression [" + matcher.group(0) + "] cannot get value.");
            }
            int start = matcher.start();
            int end = matcher.end();
            buf.append(value, position, start).append(val);
            position = end;
        }

        if (position != limit) {
            buf.append(value, position, limit);
        }

        return buf.toString();
    }

    String getProperty(String name) {
        String property = System.getProperty(name);
        if (property == null) {
            property = properties.get(name);
        }
        return property;
    }

}
